1.登录功能

image-20230810234742939.png

  • LoginController
@RestController
public class LoginController {
    @Autowired
    private EmpService empService;
    @PostMapping("/login")
    public Result login(@RequestBody Emp empPara){
        Emp emp = empService.login(empPara);
        }
        return emp!=null ?Result.success(emp):Result.error("账号或密码错误!");
    }
}
  • EmpService
Emp login(Emp empPara);
  • EmpServiceImpl
@Override
public Emp login(Emp empPara) {
    return empMapper.login(empPara);
}
  • EmpMapper
@Select("select * from emp where username=#{username} and password = #{password}")
Emp login(Emp empPara);

2.登录校验

  • 问题分析
    • 在未登录的情况下,我们也可以直接访问部门管理、员工管理等功能

image-20230810234749948.png

  • 登录标记
    • 用户登录成功之后,每一次请求中,都可以获取到该标记

2.1、会话技术

会话

  • 用户打开浏览器,访问Web服务器的资源,会话建立,直到有一方断开连接,会话结束,在一次会话中可以包含多次请求和响应

会话跟踪

  • 一种维护浏览器状态的方法,服务器需要识别多次请求是否来自同一浏览器,以便在同一次会话的多次请求间共享数据

会话跟踪方法

  • 客户端会话跟踪技术:Cookie
  • 服务端会话跟踪技术:Session
  • 令牌技术

【会话跟踪方案对比】

  • Cookie

image-20230810234755927.png

  • 优点

    • HTTP协议中支持的技术
  • 缺点

    • 移动端APP无法使用Cookie
    • 不安全,用户可以自己禁用Cookie
  • Session

image-20230810234801583.png

  • 优点
    • 存在客服端,安全
  • 缺点
    • 服务器集群环境下无法直接使用Session
    • Cookie的缺点
@RestController
public class SessionController {

    //设置Cookie
    @GetMapping("/c1")
    public Result cookie1(HttpServletResponse response){
        response.addCookie(new Cookie("login_username","itheima")); //设置Cookie/响应Cookie
        return Result.success();
    }

    //获取Cookie
    @GetMapping("/c2")
    public Result cookie2(HttpServletRequest request){
        Cookie[] cookies = request.getCookies();
        for (Cookie cookie : cookies) {
            if(cookie.getName().equals("login_username")){
                System.out.println("login_username: "+cookie.getValue()); //输出name为login_username的cookie
            }
        }
        return Result.success();
    }
    @GetMapping("/s1")
    public Result session1(HttpSession session){
        log.info("HttpSession-s1: {}", session.hashCode());

        session.setAttribute("loginUser", "tom"); //往session中存储数据
        return Result.success();
    }

    @GetMapping("/s2")
    public Result session2(HttpServletRequest request){
        HttpSession session = request.getSession();
        log.info("HttpSession-s2: {}", session.hashCode());

        Object loginUser = session.getAttribute("loginUser"); //从session中获取数据
        log.info("loginUser: {}", loginUser);
        return Result.success(loginUser);
    }
}
  • 令牌技术

image-20230810234809382.png

  • 优点
    • 支持PC端、移动端
    • 解决集群环境下的认证问题
    • 减轻服务器端存储压力
  • 缺点
    • 需要自己实现

2.2、JWT令牌

  • 概述
    • 全程:JSON Web Token(https://jwt.io/)
    • 定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的

image-20230810234815571.png

  • Base64

    • 是一种基于64个可打印字符(A-Z a-z 0-9 + /)来表示二进制数据的编码方式
  • 场景:登录验证

    1. 登录成功后,生成令牌
    2. 后续每个请求,都要携带JWT令牌,系统在每次请求处理之前,先校验令牌,通过后再处理
  • JWT生成

    • 依赖
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
@Test
public void  setJwt(){
    Map<String,Object> claims = new HashMap<>();
    claims.put("id",1);
    claims.put("name","haonan");
    String jwt = Jwts.builder()
        .signWith(SignatureAlgorithm.HS256, "mahaonan")   //设置签名算法  参数:算法方式,密钥
        .setClaims(claims) //自定义内容(载荷)
        .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000))  //设置有效期为一个小时
        .compact();
    System.out.println(jwt);
}
  • JWT校验
//解析令牌
@Test
public void parseJwt(){
    Claims claims = Jwts.parser()
            .setSigningKey("mahaonan")  //解析令牌需要的密钥(前面自己设置的)
            .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiaGFvbmFuIiwiaWQiOjEsImV4cCI6MTY4MDUyOTU0NH0.AFlglHswEkPupqSkTYyH0RQPWoH24rxzoY1JMeuNbno")
            .getBody();
    System.out.println(claims);
    System.out.println(claims.get("name"));
}
  • 【注意事项】

    • JWT校验时使用的签名密钥,必须和生成JWT令牌时使用的是一样的
    • 如果JWT令牌解析校验时报错,则说明JWT令牌被篡改或者失效了,令牌无效
  • 登录-生成令牌

    • 引入JWT令牌操作工具类
    public class JwtUtils {
    
        private static String signKey = "mahaonan";
        private static Long expire = 43200000L;
    
        /**
         * 生成JWT令牌
         * @param claims JWT第二部分负载 payload 中存储的内容
         * @return
         */
        public static String generateJwt(Map<String, Object> claims){
            String jwt = Jwts.builder()
                    .addClaims(claims)
                    .signWith(SignatureAlgorithm.HS256, signKey)
                    .setExpiration(new Date(System.currentTimeMillis() + expire))
                    .compact();
            return jwt;
        }
    
        /**
         * 解析JWT令牌
         * @param jwt JWT令牌
         * @return JWT第二部分负载 payload 中存储的内容
         */
        public static Claims parseJWT(String jwt){
            Claims claims = Jwts.parser()
                    .setSigningKey(signKey)
                    .parseClaimsJws(jwt)
                    .getBody();
            return claims;
        }
    }
    
    • 登录完成后,调用工具类生成JWT令牌,并返回
  • LoginController

@RestController
public class LoginController {
    @Autowired
    private EmpService empService;
    @PostMapping("/login")
    public Result login(@RequestBody Emp empPara){
        Emp emp = empService.login(empPara);
        if(emp!=null){
            Map<String,Object> claims = new HashMap<>();
            claims.put("id",emp.getId());
            claims.put("name",emp.getName());
            claims.put("username",emp.getUsername());
            String jwt = JwtUtils.generateJwt(claims);
            return Result.success(jwt);
        }
        return Result.error("账号或密码错误!");
    }
}

2.3、过滤器Filter

  • 概述
    • Filter过滤器是JavaWeb三大组件(Servlet,Filter,Listener)之一
    • 过滤器可以吧对资源的请求拦截下来,从而实现一些特殊的功能
    • 过滤器一般完成一些通用的操作,例如:登录校验、统一编码处理等

image-20230810234832623.png

2.3.1、快速入门

  1. 定义Filter:定义一个类,实现Filter接口,并重写其所有方法
  2. 配置Filter:Filter类上加@WebFilter注解,配置拦截资源的路径,引导类(启动类)加上@ServletComponentScan开启Servlet组件支持

  3. Application.java(启动类)

@ServletComponentScan //开启了对Servlet组件的支持
@SpringBootApplication
public class WebDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(WebDemoApplication.class, args);
    }

}
  • DemoFilter
@WebFilter("/*")
public class DemoFilter implements Filter {
    @Override   //初始化方法,只执行一次
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init 初始化方法执行");
    }

    @Override  //拦截请求后调用,调用多次
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("doFilter拦截了请求");
        //放行
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override  //销毁方法,只执行一次
    public void destroy() {
        System.out.println("destroy 销毁方法执行");
    }
}

2.3.2、详解(执行流程、拦截路径、过滤器链)

  • 执行流程

image-20230810234840037.png

  • 问题
    • 放行后访问对应资源,资源访问完成后,还会回到Filter中吗?
    • 如果回到Filter中,是重新执行还是执行放行后的逻辑代码?
  • 拦截路径
    • Filter 可以根据需求,配置不同的拦截资源路径

image-20230810234844994.png

  • 过滤器链
    • 一个Web应用中,可以配置多个过滤器,多个过滤器形成过滤器链

image-20230810234852418.png

顺序:注解配置的Filter,优先级是按照过滤器类名(字符串)的自然排序

2.3.3、登录校验-Filter

image-20230810234858209.png

  • 步骤

    1. 获取请求Url
    2. 判断请求url是否包含login,如果包含说明登录操作,放行
    3. 获取请求头中的令牌(token)
    4. 判断令牌是否存在,不存在,返回错误结果(未登录)
    5. 解析token,如果解析失败,返回错误结果(未登录)
    6. 放行
  • LoginFilter

@WebFilter("/*")
public class LoginFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        HttpServletResponse res = (HttpServletResponse) servletResponse;
        //1.获取请求URL
        String url = req.getRequestURL().toString();
        //2.判断URL中是否包含login。是的话,放行
        if(url.contains("login")){
            filterChain.doFilter(servletRequest,servletResponse);
            return;
        }
        //3.获取请求头令牌token
        String token = req.getHeader("token");
        //4.判断令牌是否存在,不存在返回错误结果
        if(!StringUtils.hasLength(token)){
            Result error = Result.error("NOT_LOGIN");
            //手动转换 对象---json
            String errorJson = JSONObject.toJSONString(error);
            res.getWriter().write(errorJson);
            return;
        }
        //5.解析token,如果解析失败则返回错误结果
        try{
            JwtUtils.parseJWT(token);
        }catch (Exception e){//解析失败
            e.printStackTrace();
            Result error = Result.error("Not_LOGIN");
            //手动转换 对象---json
            String errorJson = JSONObject.toJSONString(error);
            res.getWriter().write(errorJson);
            return;
        }
        //6.放行
        filterChain.doFilter(servletRequest,servletResponse);
    }
}

2.4、拦截器Interceptor

2.3.1、简介 & 快速入门

  • 概述
    • 是一种动态拦截方法调用的机制,类似于过滤器,Spring框架中提供的,用来动态拦截控制器方法的执行
    • 作用:拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码
  • 快速入门

    1. 定义拦截器,实现HandlerInterceptor接口,并重写其所有方法
    2. 注册拦截器
  • LoginCheckInterceptor

@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
    @Override//目标资源方法运行前运行,返回true:放行  false:不放行
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle ....");
        return true;
    }

    @Override //目标资源方法运行后运行
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle ....");
    }

    @Override //渲染视图完成后执行,最后执行的方法
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion ....");
    }
}
  • WebConfig
@Configuration //配置类
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginCheckInterceptor loginCheckInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**").excludePathPatterns("/login");
    }
}

2.3.2、详解

  • 拦截路径

    • 拦截器可以根据需求,配置不同的拦截路径
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**").excludePathPatterns("/login");
    }
    

image-20230810234908505.png

  • 执行流程

image-20230810234913294.png

  • Intercetptor与Filter
    • 接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现HandlerInterceptor接口
    • 拦截规范不同:过滤器Filter会拦截所有的资源,而Interceptor只会拦截Spring环境中的资源

2.3.3、登录校验-Interceptor

  • 步骤

    与Filter同理

@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
    @Override//目标资源方法运行前运行,返回true:放行  false:不放行
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle ....");
        //1.获取请求URL
        String url = request.getRequestURL().toString();
        //2.判断URL中是否包含login。是的话,放行
        if(url.contains("login")){
            return true;
        }
        //3.获取请求头令牌token
        String token = request.getHeader("token");
        //4.判断令牌是否存在,不存在返回错误结果
        if(!StringUtils.hasLength(token)){
            Result error = Result.error("NOT_LOGIN");
            //手动转换 对象---json
            String errorJson = JSONObject.toJSONString(error);
            response.getWriter().write(errorJson);
            return false;
        }
        //5.解析token,如果解析失败则返回错误结果
        try{
            JwtUtils.parseJWT(token);
        }catch (Exception e){//解析失败
            e.printStackTrace();
            Result error = Result.error("Not_LOGIN");
            //手动转换 对象---json
            String errorJson = JSONObject.toJSONString(error);
            response.getWriter().write(errorJson);
            return false;
        }
        return true;
    }
}

3.异常处理

  • 开发过程中不可避免会遇到异常现象

    • 比如添加部门,添加相同的名称,则会报错500——name属性在表中唯一约束
  • 出现异常,如何处理

image-20230810234922550.png

  • 方案一:在Controller的方法中进行try---catch 【不推荐 臃肿】
  • 方案二:全局异常处理器 【推荐】

image-20230810234929281.png

  • GlobalExceptionHandler
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)//捕获所有异常
    public Result ex(Exception e){
        e.printStackTrace();
        return Result.error("搞错了,你在哪儿瞎玩,联系管理员!");
    }
}

@RestControllerAdvice = @ControllerAdvice + @ResponseBody

results matching ""

    No results matching ""